package p2pmodule;

import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

import dustdispatcher.DustTask;
import events.TransferFinishedEvent;
import events.TransferFinishedListener;

/** TODO: Think about change this server for another one better (maybe FTP?).
 *  **/
/** TODO: Give the server the possibility of start the connection in order to
 * give support for the PUSH messages and be capable of stay behind firewalled
 * networks. **/
/**
 * This class represents the file server that its needed to transfer files
 * between peers in a P2P network.
 * 
 * Right now Dust just support Gnutella networks, this kind of networks don't
 * support file transfer, they just support file searching, the transfer is
 * done by other protocol. This server implements a very poor file transfer.
 * 
 * The server is listening on a port. When it receives a connection reads from
 * it the filename of the file to transfer and then sends it through the
 * connection.
 * 
 * @author ole
 */
public class FileServer extends DustTask {
	// Class constants.
	public static final short PORT = 31337; // A totally random value...
	
	// Class attributes.
	private ServerSocket socket = null;
	private List<ServerTransfer> open_transfers;
	private volatile boolean stop = false;
	private String shared_dir = null;
	
	/*
	 * Private classes.
	 */
	
	/**
	 * This Listener tells the Transfer to remove itself from our List of open
	 * transfers when it finished.
	 */
	private class RemoveFromOpenTransfers implements TransferFinishedListener {
		@Override
		public void handleEvent(TransferFinishedEvent event) {
			open_transfers.remove(event.getSource());
		}
	}

	/**
	 * Class constructor. Start the server in one of the port of the range.
	 * 
	 * @param shared_dir:
	 * 		The directory shared by this FileServer.
	 * @throws Exception:
	 * 		Throw if the shared_dir is not a directory.
	 */
	public FileServer(String shared_dir) throws Exception {
		File dir;
		
		// Check that the shared directory passed exists and is a directory.
		dir = new File(shared_dir);
		if (!dir.isDirectory())
			throw new Exception(shared_dir + " is not a directory.");
		this.shared_dir = shared_dir; 
		
		// Start the server. 
		try {
			socket = new ServerSocket(PORT);
			socket.setReuseAddress(true);
		}
		catch (Exception e) {
			throw new RuntimeException("Port already in use.");
		}
		
		open_transfers = new ArrayList<ServerTransfer>();
		setName("Dust FileServer");
	}
	
	/**
	 * Returns the name of the shared directory with the network.
	 */
	public String getSharedDir() {
		return shared_dir;
	}
	
	/**
	 * Returns the ServerSocket the server is using for listening connections.
	 */
	public ServerSocket getSocket() {
		return socket;
	}
	
	/**
	 * Makes the server start listening for connections.
	 */
	@Override
	public void run() {
		Socket new_conn;
		ServerTransfer transfer;
		RemoveFromOpenTransfers callback;
		
		// Start accepting connections until we are signaled to stop.
		while (!stop) {
			try {
				// Someone has connected to us.
				new_conn = socket.accept();
				
				// Create a Transfer to handle this connection.
				transfer = new ServerTransfer(new_conn, shared_dir);
				
				// Add the Transfer to the open transfers.
				open_transfers.add(transfer);
				
				// Add a callback to the transfer to remove itself from the
				// list when finished.
				callback = new RemoveFromOpenTransfers();
				transfer.addTransferFinishedListener(callback);
				
				// Make the transfer begin.
				transfer.start();
			}
			// We can reach here either by problems in the connection or
			// because someone calls the stopListening() method.
			catch (IOException e) {
				// We were asked to stop.
				if (stop) {
					for (ServerTransfer open_transfer: open_transfers)
						open_transfer.stopTransfer();
				}
				// Problems in the connection.
				else
					e.printStackTrace();
			}
		}
	}
	
	/**
	 * Tells the FileServer to stop listening and ends its execution.
	 */
	public void stopListening() {
		// Set the stop attribute to true to signal that we must stop.
		stop = true;
		
		// Close the socket, if the run() method was blocked at the accept()
		// method an IOException will be raised.
		try {
			socket.close();
		}
		catch (IOException e) {}
	}
}
